---
title: Hooks
description: Understanding the hook system for accessing chat state and functionality
---

The hook system provides access to chat state (e.g. message content), context, and functionality throughout the component tree without the need to pass props. These hooks enable deep customization and integration with the chat interface.

## Context Hooks

### useChatUI

The primary hook for accessing chat state and handlers to modify the chat state.

```tsx
import { useChatUI } from '@llamaindex/chat-ui'

function CustomChatComponent() {
  const {
    messages,
    input,
    setInput,
    isLoading,
    sendMessage,
    regenerate,
    stop,
    setMessages,
    requestData,
    setRequestData,
  } = useChatUI()

  const handleSendMessage = async () => {
    await sendMessage({
      id: 'user-msg-1',
      role: 'user',
      parts: [{ type: 'text', text: input }],
    })
  }

  return (
    <div>
      <p>Messages: {messages.length}</p>
      <p>Loading: {isLoading ? 'Yes' : 'No'}</p>
      {error && <p>Error: {error.message}</p>}
    </div>
  )
}
```

**Returned Properties:**

- `messages` - Array of chat messages with parts
- `input` - Current input value
- `setInput` - Function to update input
- `isLoading` - Loading state boolean (computed from status)
- `status` - Current chat status ('submitted' | 'streaming' | 'ready' | 'error')
- `sendMessage` - Function to send a message
- `stop` - Function to stop current generation
- `regenerate` - Function to regenerate a message
- `setMessages` - Function to update message array
- `requestData` - Additional request data
- `setRequestData` - Function to update request data

### useChatMessage

Access the current message context within message components.

```tsx
import { useChatMessage } from '@llamaindex/chat-ui'

function CustomMessageContent() {
  const { message, isLast } = useChatMessage()

  return (
    <div>
      <p>Role: {message.role}</p>
      <p>Parts: {message.parts.length}</p>
      <p>Is last message: {isLast ? 'Yes' : 'No'}</p>
      
      {message.parts.map((part, index) => (
        <div key={index}>
          <p>Part {index + 1}: {part.type}</p>
        </div>
      ))}
    </div>
  )
}
```

**Returned Properties:**

- `message` - Current message object with parts array
- `isLast` - Boolean indicating if this is the last message

### usePart

Access the current message content within part components. This hook provides type-safe access to specific part types in the current message.

```tsx
import { usePart } from '@llamaindex/chat-ui'

function TextPartComponent() {
  const textPart = usePart('text')
  
  if (!textPart) return null
  
  return <p>{textPart.text}</p>
}

function ArtifactPartComponent() {
  const artifactPart = usePart('data-artifact')
  
  if (!artifactPart) return null
  
  return (
    <div>
      <h4>{artifactPart.title}</h4>
      <p>Type: {artifactPart.data.type}</p>
    </div>
  )
}

function CustomPartComponent() {
  const customPart = usePart<CustomPartType>('data-custom-type')
  
  if (!customPart) return null
  
  return <CustomRenderer data={customPart.data} />
}
```

**Function Overloads:**

The hook provides automatic type inference for built-in part types:

- `usePart('text')` → `TextPart | null`
- `usePart('data-file')` → `FilePart | null`
- `usePart('data-artifact')` → `ArtifactPart | null`
- `usePart('data-event')` → `EventPart | null`
- `usePart('data-sources')` → `SourcesPart | null`
- `usePart('data-suggestion')` → `SuggestionPart | null`

**Usage Notes:**

- Must be used within a `ChatPartProvider` context
- Returns `null` if the part type doesn't match the current part
- For custom part types, use the generic parameter: `usePart<CustomPart>('custom-type')`

### useChatInput

Access input form state and handlers.

```tsx
import { useChatInput } from '@llamaindex/chat-ui'

function CustomInputField() {
  const {
    input,
    setInput,
    handleSubmit,
    isLoading,
    onFileUpload,
    uploadedFiles,
  } = useChatInput()

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        value={input}
        onChange={e => setInput(e.target.value)}
        disabled={isLoading}
        placeholder="Type your message..."
      />
      <p>Uploaded files: {uploadedFiles.length}</p>
    </form>
  )
}
```

**Returned Properties:**

- `input` - Current input value
- `setInput` - Function to update input
- `handleSubmit` - Form submit handler
- `isLoading` - Loading state
- `onFileUpload` - File upload handler
- `uploadedFiles` - Array of uploaded files

### useChatMessages

Access messages list state and handlers.

```tsx
import { useChatMessages } from '@llamaindex/chat-ui'

function CustomMessageList() {
  const { messages, isLoading, regenerate, stop, isEmpty, scrollToBottom } =
    useChatMessages()

  return (
    <div>
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i}>
            <p>Role: {msg.role}</p>
            <div>
              {msg.parts.map((part, index) => (
                <div key={index}>
                  {part.type === 'text' ? (
                    <p>{part.text}</p>
                  ) : (
                    <p>{JSON.stringify(part.data)}</p>
                  )}
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>

      {isEmpty && <p>No messages yet</p>}

      <button onClick={regenerate} disabled={isLoading}>
        Regenerate
      </button>
      <button onClick={stop}>Stop</button>
      <button onClick={scrollToBottom}>Scroll to Bottom</button>
    </div>
  )
}
```

**Returned Properties:**

- `messages` - Array of messages
- `isLoading` - Loading state
- `regenerate` - Regenerate last message
- `stop` - Stop generation
- `isEmpty` - Boolean if no messages
- `scrollToBottom` - Function to scroll to bottom

### useChatCanvas

Access artifact canvas state and handlers.

```tsx
import { useChatCanvas } from '@llamaindex/chat-ui'

function CustomCanvasControls() {
  const {
    currentArtifact,
    isVisible,
    showCanvas,
    hideCanvas,
    updateArtifact,
    artifacts,
  } = useChatCanvas()

  return (
    <div>
      <p>Canvas visible: {isVisible ? 'Yes' : 'No'}</p>
      <p>Current artifact: {currentArtifact?.title || 'None'}</p>
      <p>Total artifacts: {artifacts.length}</p>

      <button onClick={showCanvas}>Show Canvas</button>
      <button onClick={hideCanvas}>Hide Canvas</button>
    </div>
  )
}
```

**Returned Properties:**

- `currentArtifact` - Currently displayed artifact
- `isVisible` - Canvas visibility state
- `showCanvas` - Function to show canvas
- `hideCanvas` - Function to hide canvas
- `updateArtifact` - Function to update artifact
- `artifacts` - Array of all artifacts

## Utility Hooks

### useFile

Handle file uploads and processing.

```tsx
import { useFile } from '@llamaindex/chat-ui'

function FileUploadComponent() {
  const {
    uploadFile,
    uploadFiles,
    isUploading,
    uploadedFiles,
    removeFile,
    clearFiles,
  } = useFile({
    maxSize: 10 * 1024 * 1024, // 10MB
    accept: ['image/*', 'application/pdf'],
    multiple: true,
  })

  const handleFileSelect = async (files: FileList) => {
    await uploadFiles(Array.from(files))
  }

  return (
    <div>
      <input
        type="file"
        multiple
        onChange={e => handleFileSelect(e.target.files!)}
      />

      {isUploading && <p>Uploading...</p>}

      <div>
        {uploadedFiles.map((file, i) => (
          <div key={i}>
            <span>{file.name}</span>
            <button onClick={() => removeFile(i)}>Remove</button>
          </div>
        ))}
      </div>

      <button onClick={clearFiles}>Clear All</button>
    </div>
  )
}
```

**Options:**

- `maxSize` - Maximum file size in bytes
- `accept` - Array of accepted MIME types
- `multiple` - Allow multiple files
- `onUpload` - Upload completion callback
- `onError` - Error callback

**Returned Properties:**

- `uploadFile` - Upload single file
- `uploadFiles` - Upload multiple files
- `isUploading` - Upload state
- `uploadedFiles` - Array of uploaded files
- `removeFile` - Remove file by index
- `clearFiles` - Clear all files

### useCopyToClipboard

Copy content to clipboard with feedback.

```tsx
import { useCopyToClipboard } from '@llamaindex/chat-ui'

function CopyButton({ content }) {
  const { copyToClipboard, isCopied } = useCopyToClipboard({
    timeout: 2000, // Reset after 2 seconds
  })

  const handleCopy = () => {
    copyToClipboard(content)
  }

  return (
    <button
      onClick={handleCopy}
      className={isCopied ? 'text-green-600' : 'text-gray-600'}
    >
      {isCopied ? 'Copied!' : 'Copy'}
    </button>
  )
}
```

**Options:**

- `timeout` - Time before resetting copied state

**Returned Properties:**

- `copyToClipboard` - Function to copy text
- `isCopied` - Boolean indicating if recently copied

## Hook Composition

### Custom Chat Hook

Create custom hooks by composing existing ones:

```tsx
import { useChatUI, useChatMessages } from '@llamaindex/chat-ui'

function useCustomChat() {
  const chatUI = useChatUI()
  const messages = useChatMessages()

  const sendMessageWithMetadata = async (content: string, metadata: any) => {
    await chatUI.sendMessage({
      id: `user-${Date.now()}`,
      role: 'user',
      parts: [{ type: 'text', text: content }]
    }, { data: metadata })
  }

  const getLastAssistantMessage = () => {
    return chatUI.messages.filter(m => m.role === 'assistant').pop()
  }

  return {
    ...chatUI,
    ...messages,
    sendMessageWithMetadata,
    getLastAssistantMessage,
  }
}
```

## Hook Context Boundaries

### Context Availability

Different hooks are available in different contexts:

```tsx
function App() {
  return (
    <ChatSection handler={handler}>
      {/* useChatUI available here */}
      <ChatMessages>
        {/* useChatMessages available here */}
        <ChatMessages.List>
          {messages.map(message => (
            <ChatMessage key={message.id} message={message}>
              {/* useChatMessage available here */}
              <CustomMessageContent />
            </ChatMessage>
          ))}
        </ChatMessages.List>
      </ChatMessages>

      <ChatInput>
        {/* useChatInput available here */}
        <CustomInputField />
      </ChatInput>
    </ChatSection>
  )
}
```

### Error Boundaries

Handle hook errors gracefully:

```tsx
function SafeHookUsage() {
  try {
    const { messages } = useChatUI()
    return <MessageList messages={messages} />
  } catch (error) {
    console.error('Hook error:', error)
    return <ErrorFallback />
  }
}
```

## Next Steps

- [Parts](./parts.mdx) - Learn how hooks work with message parts
- [Customization](./customization.mdx) - Use hooks for custom styling and behavior
- [Examples](./examples.mdx) - See complete examples using hooks
- [Core Components](./core-components.mdx) - Understand component-hook relationships

### useWorkflow

Manages LlamaIndex workflows that are deployed on [LlamaDeploy](https://github.com/run-llama/llama_deploy). Start new instances, send events, and handle streamed event responses. Key features:

- **Client Management**: Automatically creates and manages the LlamaDeploy client connection
- **Run Lifecycle**: Creates and manages workflow runs
- **Event Streaming**: Real-time streaming of workflow events
- **State Management**: Tracks workflow status (running, complete, error) and event history
- **Error Handling**: Provides callbacks for handling errors and stop events

To get started, you need a deployment with a workflow running. You can create a deployment with a workflow by following the [LlamaDeploy documentation](https://docs.llamaindex.ai/en/stable/module_guides/llama_deploy/).

To create the hook, you'll pass the `baseUrl` of the LlamaDeploy server, the `deployment` and `workflow` name to the `useWorkflow` hook. Here's an example file:

```tsx
import { useWorkflow } from '@llamaindex/chat-ui'

function WorkflowComponent() {
  const { runId, start, stop, sendEvent, events, status } = useWorkflow({
    baseUrl: 'http://localhost:8000',
    deployment: 'my-deployment',
    workflow: 'my-workflow',
    onStopEvent: event => {
      console.log('Workflow stopped:', event)
    },
    onError: error => {
      console.error('Workflow error:', error)
    },
  })

  const handleStartWorkflow = async () => {
    await start({ message: 'Hello workflow!' })
  }

  const handleSendCustomEvent = async () => {
    await sendEvent({
      type: 'workflow.ProgressEvent',
      data: { custom: 'data' },
    })
  }

  const handleStopWorkflow = async () => {
    await stop({ reason: 'User requested stop' })
  }

  return (
    <div>
      <p>Run ID: {runId}</p>
      <p>Status: {status}</p>
      <p>Events received: {events.length}</p>

      <button onClick={handleStartWorkflow} disabled={status === 'running'}>
        Start Workflow
      </button>
      <button onClick={handleSendCustomEvent} disabled={!runId}>
        Send Event
      </button>
      <button onClick={handleStopWorkflow} disabled={status !== 'running'}>
        Stop Workflow
      </button>

      <div>
        <h4>Events:</h4>
        {events.map((event, i) => (
          <div key={i}>
            <strong>{event.type}</strong>: {JSON.stringify(event.data)}
          </div>
        ))}
      </div>
    </div>
  )
}
```

**Options:**

- `baseUrl` - Base URL for the workflow API (optional)
- `deployment` - Name of the registered deployment
- `workflow` - Set the workflow to run - if not set uses the default workflow (optional)
- `runId` - Optional run ID for resuming a previous workflow run
- `onStopEvent` - Callback when workflow receives a stop event
- `onError` - Error callback for workflow errors

**Returned Properties:**

- `runId` - Current workflow run ID
- `sendEvent` - Function to send events of type `WorkflowEvent` to the workflow. Just pass the event object with the `type` and `data` fields as parameter.
- `start` - Function to start a new workflow. This is sending a `StartEvent` besides starting the workflow run, so you pass the `data` object for the `StartEvent` as parameter.
- `stop` - Function to send stop event to current workflow. This is sending a `StopEvent` besides stopping the workflow run, so you pass the `data` object for the `StopEvent` as parameter.
- `events` - Array of all received workflow events of type `WorkflowEvent`
- `status` - Current workflow run status ('idle' | 'running' | 'complete' | 'error')

**Workflow Event Types:**

A workflow event is identified by its `type` and can have optional `data` associated with it carrying any JSON-serializable data:

```tsx
interface WorkflowEvent {
  type: string
  data?: JSONValue | undefined
}
```

The `type` field is the fully qualified name of the event type in Python.

There are a few built-in event types that you can use with the `type` field:

```tsx
// Built-in event types
enum WorkflowEventType {
  StartEvent = 'llama_index.core.workflow.events.StartEvent',
  StopEvent = 'llama_index.core.workflow.events.StopEvent',
}
```

**Advanced Usage:**

Resume an existing workflow task:

```tsx
function ResumeWorkflowComponent() {
  const workflow = useWorkflow({
    deployment: 'my-deployment',
    runId: 'existing-run-id', // Resume existing task
    onStopEvent: event => {
      console.log('Workflow completed:', event)
    },
  })

  // Events from the existing task will be automatically fetched
  useEffect(() => {
    console.log('Restored events:', workflow.events)
  }, [workflow.events])

  return (
    <div>
      <p>Resumed run: {workflow.runId}</p>
      <p>Events: {workflow.events.length}</p>
    </div>
  )
}
```

Custom event handling:

```tsx
interface CustomWorkflowEvent extends WorkflowEvent {
  type: 'workflow.ProgressEvent' | 'workflow.ResultEvent' | string
  data: {
    progress?: number
    result?: any
    message?: string
  }
}

function CustomWorkflowComponent() {
  const workflow = useWorkflow<CustomWorkflowEvent>({
    deployment: 'progress-deployment',
    workflow: 'progress-workflow',
    onStopEvent: event => {
      if (event.type === 'workflow.ResultEvent') {
        console.log('Final result:', event.data.result)
      }
    },
  })

  const progressEvents = workflow.events.filter(
    e => e.type === 'workflow.ProgressEvent'
  )
  const latestProgress =
    progressEvents[progressEvents.length - 1]?.data?.progress || 0

  return (
    <div>
      <p>Progress: {latestProgress}%</p>
      <progress value={latestProgress} max="100" />
    </div>
  )
}
```

### useChatWorkflow

The `useChatWorkflow` hook is a specialized version extending the `useWorkflow` designed specifically for chat interfaces. It is used for building chat interfaces for workflows running on [LlamaDeploy](https://github.com/run-llama/llama_deploy).
It returns a `ChatHandler`, so that it works seamlessly with the chat-ui chat components. Therefore, it is also a drop-in replacement for the [`useChat`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#usechat) hook from Vercel AI.

Here's an example of how to use it:

```tsx
import {
  ChatSection,
  ChatMessages,
  ChatMessage,
  ChatInput,
  useChatWorkflow,
} from '@llamaindex/chat-ui'

function WorkflowChatApp() {
  const handler = useChatWorkflow({
    baseUrl: 'http://localhost:4501',
    deployment: 'my-deployment',
    workflow: 'chat-workflow',
    onError: error => console.error(error),
  })

  return (
    <ChatSection handler={handler}>
      <ChatMessages>
        <ChatMessages.List>
          {handler.messages.map((message, index) => (
            <ChatMessage
              key={index}
              message={message}
              isLast={index === handler.messages.length - 1}
            >
              <ChatMessage.Avatar />
              <ChatMessage.Content>
                <ChatMessage.Part.Markdown />
                <ChatMessage.Part.Source />
                {/* Renderer for custom message parts */}
                <WeatherPart />
              </ChatMessage.Content>
              <ChatMessage.Actions />
            </ChatMessage>
          ))}
        </ChatMessages.List>
      </ChatMessages>
      <ChatInput />
    </ChatSection>
  )
}
```

**Parameters:**

- `baseUrl` - Base URL for the workflow API (optional)
- `deployment` - Name of the registered deployment
- `workflow` - Set the workflow to run - if not set uses the default workflow (optional)
- `onError` - Error callback for workflow errors

#### Chat Workflows

To be used as a chat workflow, your workflows need to follow the same calling convention as a [Agent Workflow](https://docs.llamaindex.ai/en/stable/understanding/agent) - that's why Agent Workflows are working out of the box with the `useChatWorkflow` hook.

Your workflow will be executed once for each chat request with the following input parameters included in workflow's `StartEvent`:

- `user_msg` [str]: The current user message
- `chat_history` [list[[ChatMessage](https://docs.llamaindex.ai/en/stable/api_reference/prompts/#llama_index.core.prompts.ChatMessage)]]: All the previous messages of the conversation

Example:

```python
@step
def handle_start_event(ev: StartEvent) -> MyNextEvent:
    user_msg = ev.user_msg
    chat_history = ev.chat_history
    ...
```

#### Built-in Workflow Events

The hook automatically processes workflow events (using `useWorkflow`) and renders them as parts in the chat interface, making it easy to build rich conversational experiences with LlamaDeploy workflows.
Your LlamaDeploy workflows can send three main types of events to enhance the chat experience:

##### 1. SourceNodesEvent - Citations and References

Send source nodes to display citations and references for generated content:

```python
from llama_index.core.chat_ui.events import (
    SourceNodesEvent,
)
from llama_index.core.schema import NodeWithScore
from llama_index.core.data_structs import Node

ctx.write_event_to_stream(
    SourceNodesEvent(
        nodes=[
            NodeWithScore(
                node=Node(
                    text="sample node content",
                    metadata={"URL": "https://example.com/document.pdf"},
                ),
                score=0.8,
            ),
        ],
    )
)
```

> Note: Your `ChatMessage.Content` component needs to have the `ChatMessage.Part.Source` component as a child to display the citations and references (as shown in the example above).

##### 2. ArtifactEvent - Code and Artifacts

Send code snippets, documents, or other artifacts that can be displayed in a dedicated canvas:

```python
from llama_index.core.chat_ui.events import (
    ArtifactEvent,
)
import time

ctx.write_event_to_stream(
    ArtifactEvent(
        data={
            "type": "code",
            "created_at": int(time.time()),
            "data": {
                "language": "typescript",
                "file_name": "example.ts",
                "code": 'console.log("Hello, world!");',
            },
        }
    )
)
```

> Note: Your `ChatMessage.Content` component needs to have the `ChatMessage.Part.Markdown` component as a child to display the artifacts inline in the markdown component (as shown in the example above).

##### 3. UIEvent - Custom UI Components

Send custom data to render it in a specialized UI components. In your workflow code, you can send `UIEvent`s for this - for example to render a weather part in the chat interface:

```python
from llama_index.core.chat_ui.events import (
    UIEvent,
)
from pydantic import BaseModel

class WeatherData(BaseModel):
    location: str
    temperature: float
    condition: str
    humidity: int
    windSpeed: int

weather_data = WeatherData(
    location="San Francisco, CA",
    temperature=22,
    condition="sunny",
    humidity=65,
    windSpeed=12,
)

ctx.write_event_to_stream(
    UIEvent(type="weather", data=weather_data)
)
```

To render this custom UI component, you need to add it as child to your `ChatMessage.Content` component. The example above will render the [`WeatherPart`](../../examples/llamadeploy/chat/ui/components/custom/custom-weather.tsx) component in the chat interface.
